import type { CountryDetail, FacilityType } from "@dci/core"; import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { Breadcrumbs } from "@/components/entity/breadcrumbs"; import { EntityMap } from "@/components/entity/entity-map"; import { JsonLd } from "@/components/entity/json-ld"; import { fPageHref, PagedFacilities, parseFPage } from "@/components/entity/paged-facilities"; import { SectionNav } from "@/components/entity/section-nav"; import { SourcesFooter } from "@/components/entity/sources-footer"; import { EventList } from "@/components/event-list"; import { ClaimsTable } from "@/components/evidence"; import { FacilityTable } from "@/components/facility-table"; import { EnergyContextPanel } from "@/components/intelligence/country/energy"; import { CoverageBar, CoverageStrip, GridConstraintList, HonestEmpty, PillLink, PipelineBars, PublishedOnlyNote, ShareList } from "@/components/intelligence/shared"; import { WatchButton } from "@/components/intelligence/watch-button"; import { ProjectList } from "@/components/project-list"; import { Badge, EstimateMark } from "@/components/ui/badge"; import { Page, Section } from "@/components/ui/card"; import { StackedBar } from "@/components/ui/charts"; import { CountBarList } from "./bars"; import { CountryFlag } from "@/components/ui/country-flag"; import { Metric, MetricCell, MetricRow } from "@/components/ui/metric"; import { EmptyState, ErrorState, OfflineNotice } from "@/components/ui/states"; import { RowLink, TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table"; import { api } from "@/lib/api"; import type { DetailFacilitiesMeta } from "@/lib/dto"; import { fmtCompact, fmtDate, fmtInt, fmtMw, fmtPct, fmtUsd, pluralize, splitMw } from "@/lib/format"; import { OPERATOR_KIND_LABEL, TYPE_LABEL } from "@/lib/labels"; import { routes } from "@/lib/routes"; import { compactJsonLd, NOT_FOUND_METADATA, pageMetadata } from "@/lib/seo"; import type { SearchParams } from "@/lib/sort"; import { GrowthChart } from "./growth-chart"; export const revalidate = 300; type Props = { params: Promise<{ slug: string }>; searchParams: Promise }; const HUGE = new Set(["US", "CA", "RU", "CN", "AU", "BR"]); const SMALL = new Set(["SG", "HK", "LU", "MT", "BH", "QA", "AE", "IL", "NL", "BE", "CH", "IE", "DK", "TW", "KW", "CY", "EE", "LV", "LT", "SI", "PR", "IS"]); // ≥ 5 so the API returns grid clusters instead of one country-level bubble const zoomFor = (iso2: string) => (HUGE.has(iso2) ? 5 : SMALL.has(iso2) ? 7 : 5.5); function describe(c: CountryDetail): string { const bits = [`${fmtInt(c.facilityCount)} data center ${pluralize(c.facilityCount, "facility", "facilities")}`]; if (c.knownMw) bits.push(`${fmtMw(c.knownMw)} known operational capacity (${fmtPct(c.mwCoverage)} of facilities publish a figure)`); const pipe = (c.constructionMw ?? 0) + (c.plannedMw ?? 0); if (pipe) bits.push(`≈ ${fmtMw(pipe)} in the pipeline`); bits.push(`${fmtInt(c.operatorCount)} ${pluralize(c.operatorCount, "operator")}`); bits.push(`${fmtInt(c.cloudRegionCount)} cloud ${pluralize(c.cloudRegionCount, "region")}`); if (c.hyperscaleCount) bits.push(`${fmtInt(c.hyperscaleCount)} hyperscale`); return `Data centers in ${c.name}: ${bits.join(", ")}. Markets, operators, project pipeline, AI infrastructure, cloud footprint, internet exchanges, energy context and grid constraints — every figure sourced.`; } export async function generateMetadata({ params }: Props): Promise { const { slug } = await params; const r = await api.country(slug); if (!r.ok) return r.status === 404 ? NOT_FOUND_METADATA : { title: "Country", alternates: { canonical: routes.country(slug) } }; return pageMetadata({ title: `Data Centers in ${r.data.name}`, description: describe(r.data), path: routes.country(r.data.slug) }); } function Mw({ mw, estimate }: { mw: number | null | undefined; estimate?: boolean }) { const s = splitMw(mw); if (!s) return —; return ( <> {estimate && } {s.value} ); } const unitOf = (mw: number | null | undefined) => splitMw(mw)?.unit ?? undefined; const IXP_ROWS = 15; export default async function CountryPage({ params, searchParams }: Props) { const [{ slug }, sp] = await Promise.all([params, searchParams]); const fPage = parseFPage(sp.fPage); const r = await api.country(slug, fPage > 1 ? { fPage } : undefined); if (!r.ok && r.status === 404) notFound(); if (!r.ok) { return ( ); } const c = r.data; const meta = (r.meta ?? {}) as DetailFacilitiesMeta; const facTotal = meta.facilitiesTotal ?? c.facilityCount; const facPer = meta.facilitiesPerPage ?? 50; const base = routes.country(c.slug); const statusTotal = Object.values(c.statusBreakdown).reduce((a, b) => a + (b ?? 0), 0); const types = (Object.entries(c.typeBreakdown) as Array<[FacilityType, number]>).filter(([, n]) => n > 0).sort((a, b) => b[1] - a[1]); const metros = [...c.metros].sort((a, b) => b.facilityCount - a.facilityCount); const topOps = [...c.topOperators].sort((a, b) => b.facilityCount - a.facilityCount).slice(0, 10); const cloud = [...c.cloudRegions].sort((a, b) => a.provider.name.localeCompare(b.provider.name) || a.code.localeCompare(b.code)); const ixps = [...(c.ixps ?? [])].sort((a, b) => b.facilityCount - a.facilityCount || a.name.localeCompare(b.name)); const ixpCount = c.ixpCount ?? ixps.length; const aiFacilities = c.aiFacilities ?? []; const aiProjects = c.aiProjects ?? []; const gridConstraints = c.gridConstraints ?? []; const claims = c.claims ?? []; const hasAi = aiFacilities.length > 0 || aiProjects.length > 0; const facTotalForShare = c.facilityCount || 1; const jsonLd = compactJsonLd({ "@context": "https://schema.org", "@type": "Country", name: c.name, identifier: c.iso2, url: `${routes.country(c.slug)}`, description: describe(c), geo: c.lat !== null && c.lng !== null ? { "@type": "GeoCoordinates", latitude: c.lat, longitude: c.lng } : undefined, }); const sections = [ { id: "map", label: "Map", count: c.facilityCount }, { id: "growth", label: "Growth" }, { id: "breakdown", label: "Breakdown", count: statusTotal }, { id: "markets", label: "Markets", count: metros.length }, { id: "operators", label: "Operators", count: topOps.length }, { id: "pipeline", label: "Pipeline" }, { id: "ai", label: "AI", count: hasAi ? aiFacilities.length + aiProjects.length : 0 }, { id: "cloud", label: "Cloud", count: cloud.length }, { id: "connectivity", label: "Connectivity" }, { id: "projects", label: "Projects", count: c.recentProjects.length }, { id: "changes", label: "Changes", count: c.recentEvents.length }, { id: "energy", label: "Energy" }, { id: "grid", label: "Grid" }, { id: "coverage", label: "Coverage" }, { id: "facilities", label: "Facilities", count: facTotal }, { id: "claims", label: "Claims", count: claims.length }, ]; return (
Country · {c.iso2}

Data centers in {c.name}

{(c.region || c.subregion) && {[c.region, c.subregion].filter((v, i, a): v is string => Boolean(v) && a.indexOf(v) === i).join(" · ")}} {c.population !== null && ( {fmtCompact(c.population)} residents )} {c.gdpUsd !== null && ( {fmtUsd(c.gdpUsd)} GDP )} {(c.population !== null || c.gdpUsd !== null) && World Bank}
Explore →
} unit={unitOf(c.knownMw)} label="Operational MW" note={} /> } unit={unitOf(c.constructionMw)} label="Under construction" note={`${fmtInt(c.constructionCount)} ${pluralize(c.constructionCount, "site")}${c.constructionMw === null && c.constructionCount ? ", MW not published" : ""}`} /> } unit={unitOf(c.plannedMw)} label="Planned MW" note={`${fmtInt(c.plannedCount)} planned ${pluralize(c.plannedCount, "site")}${c.plannedMw === null && c.plannedCount ? ", MW not published" : ""}`} /> {c.rankings.length > 0 && (
Rankings {c.rankings.map((rk) => ( #{rk.rank} /{rk.total} {rk.label} ))}
)}
Status
{statusTotal ? : }
Type
{types.length ? ({ label: TYPE_LABEL[t], value: n, href: routes.datacenters(`country=${c.iso2}&type=${t}`) }))} showRank={false} /> : }
{metros.length > 0 && (
All markets →}> {metros.map((m) => { const pipe = m.constructionMw === null && m.plannedMw === null ? null : (m.constructionMw ?? 0) + (m.plannedMw ?? 0); return ( ); })}
Market Facilities Known MW Pipeline Cloud regions IXPs Operators
{m.name} {m.regionName && m.regionName !== m.name && {m.regionName}} {fmtInt(m.facilityCount)} {m.knownMw === null ? — : fmtMw(m.knownMw)} {pipe === null ? ( — ) : ( <> {fmtMw(pipe)} )} {fmtInt(m.cloudRegionCount)} {fmtInt(m.ixpCount)} {fmtInt(m.operatorCount)}
)} {topOps.length > 0 && (
All facilities →}> ({ key: o.id, label: <>{o.name}{o.kind && {OPERATOR_KIND_LABEL[o.kind]}}, href: routes.operator(o.slug), count: o.facilityCount, share: o.facilityCount / facTotalForShare, mw: o.knownMw ?? null }))} unit="facilities" />
)}
All projects →}> {c.pipeline ? : }
{hasAi && (
AI infrastructure index →}> {aiFacilities.length > 0 && (
Facilities ({fmtInt(aiFacilities.length)})
)} {aiProjects.length > 0 && (
Projects ({fmtInt(aiProjects.length)})
)}
)} {cloud.length > 0 && (
All cloud regions →}> {cloud.map((cr) => ( ))}
Provider Code Name City AZs Status Launched
{cr.provider.name} {cr.name} {cr.code} {cr.name} {cr.city ?? "—"} {cr.availabilityZones === null ? — : fmtInt(cr.availabilityZones)} {cr.status} {cr.isSovereign && ( Sovereign )} {cr.launchedOn ? fmtDate(cr.launchedOn) : "—"}
)}
All {fmtInt(ixpCount)} IXPs →}> {ixps.length ? ( <> {ixps.slice(0, IXP_ROWS).map((x) => ( ))}
Exchange City Networks Facilities
{x.name} {x.nameLong && x.nameLong !== x.name && {x.nameLong}} {x.city ?? (x.metro?.name ?? "—")} {x.networkCount === null ? — : fmtInt(x.networkCount)} {fmtInt(x.facilityCount)}
{ixps.length > IXP_ROWS && (

First {IXP_ROWS} of {fmtInt(ixps.length)} exchanges by facility presence —{" "} see all .

)} ) : ( )}

Network counts are shown only when the exchange publishes them.{" "} Connectivity index

{c.recentProjects.length > 0 && (
All projects →}>
)} {c.recentEvents.length > 0 && (
Live feed →}>
)}
Power & grid →}> {c.energy ? : }
Coverage report →}> {c.coverage ? : }
Filter & export →}> fPageHref(base, sp, p)} columns={["operator", "market", "status", "mw", "type", "confidence"]} />
{claims.length > 0 && (
)}
); }